/*
 * Copyright (c) 2018 Hitevision
 */

package tw.com.hitevision.whiteboard.android.state;

import android.content.Context;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.Log;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import tw.com.hitevision.whiteboard.android.Whiteboard;
import tw.com.hitevision.whiteboard.android.render.BaseItem;
import tw.com.hitevision.whiteboard.android.stateFile.WhiteboardStateFile;
import tw.com.hitevision.whiteboard.android.storage.FileHandler;
import tw.com.hitevision.whiteboard.android.storage.FileHelper;
import tw.com.hitevision.whiteboard.android.util.IdGenerator;
import tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.BasicRuler;
import tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementToolLayer;
import tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementToolListener;
import tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementToolRecord;
import tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.Protractor;
import tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.Ruler;
import tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.RulerTriangle;

import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.PROTRACTOR_DEGREE_180;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.PROTRACTOR_DEGREE_360;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.RULER;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.TRIANGLE_PROTRACTOR_RULER;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.TRIANGLE_RULER_30;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.TRIANGLE_RULER_45;

public class WhiteboardState {
    private static final String TAG = WhiteboardState.class.getSimpleName();

    public static final long INVALID_ID = -1L;

    private final long id;

    /** A list of items to render. */
    private final List<BaseItem> items;

    /** The pageProperty of this state. */
    private final PageProperty pageProperty;

    /** An array of recent actions. */
    private final Deque<Action> actionStack;

    /** An array of recent actions. */
    private final Deque<Action> redoStack;

    private String previewFile;

    private boolean isAnnotationState;

    private boolean isPreviewDirty;

    private boolean isScreenshotTook;

    private int backgroundX;
    private int backgroundY;

    // save the current canvas scaling rate
    private float appliedScale;

    private WhiteboardStateFile stateFile;
    private String customBackgroundName;

    private ArrayList<MeasurementToolRecord> measurementToolRecords;

    public WhiteboardState(PageProperty.Color color, PageProperty.Pattern pattern,
                           @Nullable Drawable background, @Nullable String previewFile,
                           boolean isAnnotationState) {
        id = IdGenerator.createId();
        items = new CopyOnWriteArrayList<>();
        actionStack = new ArrayDeque<>();
        redoStack = new ArrayDeque<>();
        isPreviewDirty = true;

        pageProperty = new PageProperty(color, pattern);
        pageProperty.setCustomBackground(background);

        this.previewFile = previewFile;
        this.isAnnotationState = isAnnotationState;

        this.appliedScale = 1.0f;
    }

    public long getId() {
        return id;
    }

    public void setPreviewDirty(boolean dirty) {
        isPreviewDirty = dirty;
    }

    public boolean isPreviewDirty() {
        return isPreviewDirty;
    }

    public boolean isAnnotationState() {
        return isAnnotationState;
    }

    public boolean isScreenshotTook(){
        return isScreenshotTook;
    }

    public void setScreenshotTook(boolean b){
        isScreenshotTook = b;
    }

    public void removeScreenshotFile(FileHandler fileHandler){
        fileHandler.sendDeleteImageMessage(previewFile);
    }

    public void setBackgroundPosition(int x, int y) {
        backgroundX = x;
        backgroundY = y;
    }

    public int getBackgroundX() {
        return backgroundX;
    }

    public int getBackgroundY() {
        return backgroundY;
    }

    @Nullable
    public String getPreviewFile() {
        return previewFile;
    }

    public void setPreviewFile(String fileName) {
        previewFile = fileName;
    }

    @ColorRes
    public int getPageColorRes() {
        return pageProperty.getColorRes();
    }

    public void setPageColor(PageProperty.Color color) {
        pageProperty.setColor(color);
    }

    @DrawableRes
    public int getPagePatternRes() {
        return pageProperty.getPatternRes();
    }

    public PageProperty.Color getPageColor() {
        return pageProperty.getColor();
    }

    public void setPagePattern(PageProperty.Pattern pattern) {
        pageProperty.setPattern(pattern);
    }

    public String getCustomBackgroundName() {
        return customBackgroundName;
    }

    public void setCustomBackgroundName(String name) {
        customBackgroundName = name;
    }

    public PageProperty.Pattern getPagePattern() {
        return pageProperty.getPattern();
    }

    public boolean isPageCustomBackgroundSet() {
        return pageProperty.isCustomBackgroundSet();
    }

    public void setBackgroundDrawable(Drawable drawable) {
        pageProperty.setCustomBackground(drawable);
    }

    public Drawable getPageCustomBackground() {
        return pageProperty.getCustomBackground();
    }

    public boolean hasPagePattern() {
        return pageProperty.isCustomBackgroundSet() || pageProperty.getPatternRes() != 0;
    }

    public boolean isPageColorDark() {
        return pageProperty.isDark();
    }

    /**
     * Returns a list of items.
     */
    public List<BaseItem> getItems() {
        return items;
    }

    public void addItem(BaseItem item) {
        items.add(item);
    }

    public void addItems(ArrayList<BaseItem> items){
        this.items.addAll(items);
    }

    public void removeItem(BaseItem item) {
        items.remove(item);
    }

    @Nullable
    public BaseItem getItem(long id) {
        for (BaseItem item : items) {
            if (item.getId() == id) {
                return item;
            }
        }
        return null;
    }

    /*
    should put delete item's index id in deletedIndexes
     */
    public void removeItems(int[] deletedIndexes) {
        for (int i = deletedIndexes.length - 1; i >= 0; i--) {
            final int index = deletedIndexes[i];
            if (index < items.size()) {
                items.remove(index);
            }
        }
    }

    public void clearItems() {
        items.clear();
    }

    public void paste(List<BaseItem> scrapbook) {
        for (int i = 0; i < scrapbook.size(); i++) {
            items.add(scrapbook.get(i).copy());
        }
    }

    /**
     * Inserts copies of items in scrapbook.
     *
     * @return an array of ids of newly created copies
     */
    public long[] pasteOffset(List<BaseItem> scrapbook, float dx, float dy) {
        BaseItem copy;
        long[] ids = new long[scrapbook.size()];
        for (int i = 0; i < scrapbook.size(); i++) {
            copy = scrapbook.get(i).copy();
            copy.moveBy(dx, dy);
            ids[i] = copy.getId();
            items.add(copy);
        }
        return ids;
    }

    public void getSelectedItems(List<BaseItem> out) {
        out.clear();

        for (BaseItem item : items) {
            if (item.isSelected()) {
                out.add(item);
            }
        }
    }

    public void clearSelected() {
        for (BaseItem item : items) {
            item.setSelected(false);
        }
    }

    public boolean hasSelectedItems() {
        for (BaseItem item : items) {
            if (item.isSelected()) {
                return true;
            }
        }

        return false;
    }

    public void undo(Context context, Whiteboard.Input input) {
        if (actionStack.isEmpty()) {
            return;
        }

        Action action = actionStack.pop();
        action.undo(items, context, input);
        redoStack.push(action);

        if (action.getType().equals(Action.Type.TEXT_REMOVE)) {
            if (!actionStack.isEmpty() && actionStack.peek().getType().equals(Action.Type.TEXT)) {
                action = actionStack.pop();
                action.undo(items, context, input);
                redoStack.push(action);
            }
        }
    }

    public void redo(Context context, Whiteboard.Input input) {
        log("redo: redoStack.size=" + redoStack.size());
        if (redoStack.isEmpty()) {
            return;
        }

        Action action = redoStack.pop();
        action.redo(items, context, input);
        actionStack.push(action);

        if (action.getType().equals(Action.Type.TEXT)) {
            if (!redoStack.isEmpty() && redoStack.peek().getType().equals(Action.Type.TEXT_REMOVE)) {
                action = redoStack.pop();
                action.redo(items, context, input);
                actionStack.push(action);
            }
        }
    }

    public void addAction(Action action) {
        actionStack.push(action);

        if (!redoStack.isEmpty()) {
            redoStack.clear();
        }
    }

    public void recycle() {
        // do nothing
    }

    /**
     * Creates a copy of this {@code WhiteboardState} instance.
     */
    public WhiteboardState copy(Context context, FileHandler fileHandler) {
        final WhiteboardState state = new WhiteboardState(pageProperty.getColor(),
                pageProperty.getPattern(), pageProperty.getCustomBackground(),
                FileHelper.getInstance(context).generateFileName(isAnnotationState), isAnnotationState);

        fileHandler.sendCopyPreviewMessage(previewFile, null, state.previewFile);

        for (BaseItem item : items) {
            state.items.add(item.copy());
        }

        state.setAppliedScale(appliedScale);

        state.customBackgroundName = customBackgroundName;

        state.setScreenshotTook(true);

        return state;
    }

    public void updateUndoActionTransform(float scrollX, float scrollY, float zoomScale, PointF zoomPointF) {
        for (Action action : actionStack) {
            action.updateUndoActionTransform(scrollX, scrollY, zoomScale, zoomPointF, items);
        }
    }

    public void updateRedoActionTransform(float scrollX, float scrollY, float zoomScale, PointF zoomPointF) {
        for (Action action : redoStack) {
            action.updateRedoActionTransform(scrollX, scrollY, zoomScale, zoomPointF, items);
        }
    }

    public boolean isUndoStackEmpty() {
        return actionStack.isEmpty();
    }

    public boolean isRedoStackEmpty() {
        return redoStack.isEmpty();
    }

    public void setAppliedScale(float scale) {
        appliedScale = scale;
    }

    public float getAppliedScale() {
        return appliedScale;
    }

    public WhiteboardStateFile createStateFile() {
        if (stateFile == null) {
            stateFile = new WhiteboardStateFile();
        }

//        stateFile.setStateItems(items);
        List<BaseItem> copy = new ArrayList<>(items);
        stateFile.setStateItems(copy);
        stateFile.setColor(pageProperty.getColor());
        stateFile.setPattern(pageProperty.getPattern());
        stateFile.setCustomBackgroundName(customBackgroundName);
        stateFile.setPreviewFile(previewFile);

        return stateFile;
    }

    public void clearUndoRedoStack() {
        if (actionStack != null) {
            actionStack.clear();
        }
        if (redoStack != null) {
            redoStack.clear();
        }
    }

    public void removeMeasurementToolRecord(BasicRuler ruler) {
        if (ruler == null || measurementToolRecords == null) {
            return;
        }
        for (int i = 0; i < measurementToolRecords.size(); i++) {
            MeasurementToolRecord r = measurementToolRecords.get(i);
            if (r.getName().equals(ruler.getName())) {
                measurementToolRecords.remove(i);
                return;
            }
        }
    }

    public void updateMeasurementToolRecord(BasicRuler ruler) {
        if (ruler == null) {
            return;
        }
        if (measurementToolRecords == null) {
            measurementToolRecords = new ArrayList<>();
        }
        for (int i = 0; i < measurementToolRecords.size(); i++) {
            MeasurementToolRecord r = measurementToolRecords.get(i);
            if (r != null && r.getName().equals(ruler.getName())) {
                measurementToolRecords.set(i, ruler.createMeasurementToolRecord());
                return;
            }
        }
        MeasurementToolRecord record = ruler.createMeasurementToolRecord();
        measurementToolRecords.add(record);
    }

    public BasicRuler[] loadMeasurementToolRecord(Context baseContext, MeasurementToolLayer layer,
                                                  MeasurementToolListener rulerListener) {
        if (measurementToolRecords == null) {
            measurementToolRecords = new ArrayList<>();
        }

        Ruler ruler = null;
        RulerTriangle rulerTriangle30 = null;
        RulerTriangle rulerTriangle45 = null;
        Protractor protractor180 = null;
        Protractor protractor360 = null;
        RulerTriangle rulerTriangleProtractor = null;

        for (MeasurementToolRecord record : measurementToolRecords) {
            if (record == null) {
                continue;
            }
            BasicRuler basicRuler = null;
            if (record.getName().equals(RULER)) {
                ruler = new Ruler(baseContext, rulerListener);
                basicRuler = ruler;
            } else if (record.getName().equals(TRIANGLE_RULER_30)) {
                rulerTriangle30 = new RulerTriangle(baseContext, RulerTriangle.TriangleType.DEGREE30, rulerListener);
                basicRuler = rulerTriangle30;
            } else if (record.getName().equals(TRIANGLE_RULER_45)) {
                rulerTriangle45 = new RulerTriangle(baseContext, RulerTriangle.TriangleType.DEGREE45, rulerListener);
                basicRuler = rulerTriangle45;
            } else if (record.getName().equals(PROTRACTOR_DEGREE_180)) {
                protractor180 = new Protractor(baseContext, Protractor.ProtractorMode.DEGREE180, rulerListener);
                basicRuler = protractor180;
            } else if (record.getName().equals(PROTRACTOR_DEGREE_360)) {
                protractor360 = new Protractor(baseContext, Protractor.ProtractorMode.DEGREE360, rulerListener);
                basicRuler = protractor360;
            } else if (record.getName().equals(TRIANGLE_PROTRACTOR_RULER)) {
                rulerTriangleProtractor = new RulerTriangle(baseContext, RulerTriangle.TriangleType.TRIANGLE_PROTRACTOR, rulerListener);
                basicRuler = rulerTriangleProtractor;
            }
            if (basicRuler != null) {
                layer.addRuler(basicRuler);
                basicRuler.loadMeasurementToolRecord(record);
            }
        }

        return new BasicRuler[]{ruler, rulerTriangle30, rulerTriangle45, protractor180, protractor360, rulerTriangleProtractor};
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
    }
}
